package com.dx.financehy.aspect;

import com.dx.financehy.annotation.Prevent;
import com.dx.financehy.exception.BadRequestException;
import com.dx.financehy.utils.RedisUtils;
import com.dx.financehy.utils.security.SecurityUtils;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;

/**
 * author：DFH
 * description: 幂等性校验
 * use : 1、p0.属性参数 或 入参名称.属性参数 例： #p0.name 、 #req.name (若多个参数，例如 R test(String name，String test) ， name 为 p0，test 为 p1)
 * 2、调用无入参bean方法 @serviceImpl.test()
 * 3、调用有入参bean方法 @serviceImpl.test(#p0.name)
 **/
@Aspect
@Component
@Slf4j
public class PreventAspect implements BeanFactoryAware {

    /**
     * bean工场
     */
    private BeanFactory beanFactory;

    /**
     * 用于SpEL表达式解析.
     */
    private static final SpelExpressionParser PARSER = new SpelExpressionParser();

    /**
     * 用于获取方法参数定义名字.
     */
    private static final DefaultParameterNameDiscoverer NAME_DISCOVERER = new DefaultParameterNameDiscoverer();

    @Resource
    private RedisUtils redisUtil;

    @Pointcut("@annotation(com.dx.financehy.annotation.Prevent)")
    public void prevent() {

    }

    @Before("prevent()")
    public void doBefore(JoinPoint joinPoint) throws Exception {
        // 通过joinPoint获取被注解方法
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        Prevent prevent = method.getAnnotation(Prevent.class);

        //构造key 暂定key = 注解prefix（可省略） + 方法名 + 注解suffix（可省略，不填默认userid） start
        StringBuilder buffer = new StringBuilder("");
        if (!"".equals(prevent.prefix())) {
            buffer.append(prevent.prefix().toUpperCase());
        }

        buffer.append(method.getName().toUpperCase());

        // 使用spring的DefaultParameterNameDiscoverer获取方法形参名数组
        String[] paramNames = NAME_DISCOVERER.getParameterNames(method);
        if (!"".equals(prevent.suffix()) && paramNames != null) {
            // 解析过后的Spring表达式对象
            Expression expression = PARSER.parseExpression(prevent.suffix());
            // spring的表达式上下文对象
            StandardEvaluationContext context = new StandardEvaluationContext();
            context.setBeanResolver(new BeanFactoryResolver(beanFactory));
            // 通过joinPoint获取被注解方法的形参
            Object[] args = joinPoint.getArgs();
            // 给上下文赋值
            for (int i = 0; i < args.length; i++) {
                context.setVariable("p" + i, args[i]);
                context.setVariable(paramNames[i], args[i]);
            }
            Object value = expression.getValue(context);
            if (value != null) {
                buffer.append(":").append(value);
            } else {
                throw new Exception("参数名称填写错误");
            }
        } else if (prevent.isSuffixUserId()) {
            String value = SecurityUtils.getCurrentUserId();
            buffer.append(":").append(value);
        }


        String key = buffer.toString();
        //end

        if (!redisUtil.setNx(key, "1", prevent.time(), prevent.unit())) {
            throw new BadRequestException(prevent.message());
        }
    }

    @Override
    public void setBeanFactory(@NonNull BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }
}
